using System.Linq;
using System.Threading;
using System.Reflection;
using System.Collections.Generic;
using log4net;

namespace Parallel
{
    public class ParallelExecutor<T> where T : class
    {
        readonly IList<ManualResetEvent> _itemEvents = new List<ManualResetEvent>();
        readonly IList<ParallelExecutable<T>> _executables = new List<ParallelExecutable<T>>();
        readonly IList<T> _parallelItems = new List<T>();
        readonly IList<T> _serialItems = new List<T>();
        string _methodName;
        readonly ILog _logger = LogManager.GetLogger(typeof(ParallelExecutor<T>));

        public ParallelExecutor() : this("Execute") {}
        public ParallelExecutor(string methodName)
        {
            SetAction(methodName);
        }

        public void AddParallel(T item)
        {
            _parallelItems.Add(item);
        }

        public void AddSerial(T item)
        {
            _serialItems.Add(item);
        }

        public void SetAction(string methodName)
        {
            _methodName = methodName;
        }

        public void Execute()
        {
            if (!Init()) return;

            _logger.Info("About to start all parallel executables");

            foreach (var executable in _executables)
                new Thread(executable.Execute).Start();

            if (_itemEvents.Count > 0)
                WaitHandle.WaitAll(_itemEvents.ToArray());
        }

        bool Init()
        {
            _itemEvents.Clear();

            MethodInfo method;
            try
            {
                method = typeof(T).GetMethod(_methodName);
            }
            catch { return false; }

            if (_serialItems.Count > 0)
            {
                var executable = new ParallelExecutable<T>(method, _serialItems, GetEvent());
                AddExecutable(executable);
            }

            foreach (var item in _parallelItems)
            {
                var executable = new ParallelExecutable<T>(method, item, GetEvent());
                AddExecutable(executable);
            }

            return true;
        }

        ManualResetEvent GetEvent()
        {
            var mre = new ManualResetEvent(false);
            _itemEvents.Add(mre);

            return mre;
        }

        void AddExecutable(ParallelExecutable<T> executable)
        {
            _executables.Add(executable);
        }
    }
}